/**
 * @file cdnet_l1.c
 * @brief CDNET Layer 1协议实现 - 16位端口寻址
 * @details CDNET Layer 1协议的完整实现，支持16位端口寻址、
 *          组播通信和多网络路由功能
 * 
 * @author 修改团队：mastwet@Undefine Innovation
 * @date 2024
 * @copyright MIT License
 * @note 基于Duke Fong的cdnet_l1实现
 */

#include "cdnet.h"

/**
 * @brief 写入Layer 1协议头部
 * @param pkt CDNET数据包指针
 * @param hdr 头部缓冲区指针
 * @return 头部大小（字节数），失败返回错误码
 * 
 * @details Layer 1头部格式：
 * [头部标志(1字节)][可选:源网络地址(2字节)][可选:目标网络地址(2字节)]
 * [源端口低字节][可选:源端口高字节][目标端口低字节][可选:目标端口高字节]
 * 
 * 头部标志位定义：
 * - bit7: 必须为1（Layer 1标志）
 * - bit6-5: 多播类型（00=None, 01=组播, 10=网络多播, 11=组播+网络多播）
 * - bit4: 源网络地址存在标志
 * - bit3: 目标网络地址存在标志
 * - bit2: 源端口高字节存在标志
 * - bit1: 目标端口高字节存在标志
 * - bit0: 保留位
 */
int cdn1_hdr_w(const cdn_pkt_t *pkt, uint8_t *hdr)
{
    const cdn_sockaddr_t *src = &pkt->src;
    const cdn_sockaddr_t *dst = &pkt->dst;
    uint8_t *buf = hdr + 1;
    cdn_multi_t multi = CDN_MULTI_NONE;

    /* 分析多播类型 */
    if (src->addr[0] == 0xa0)
        multi |= CDN_MULTI_NET;  /* 源地址为网络多播 */
    if (dst->addr[0] == 0xf0)
        multi |= CDN_MULTI_CAST; /* 目标地址为组播 */

    /* 构建头部标志字节 */
    hdr[0] = 0x80 | (multi << 4); /* bit7=1, bit6-5=多播类型 */

    /* 添加源网络地址（如需要） */
    if (multi & CDN_MULTI_NET) {
        *buf++ = src->addr[1];  /* 源网络号 */
        *buf++ = src->addr[2];  /* 源MAC地址 */
    }
    
    /* 添加目标网络地址（如需要） */
    if (multi != CDN_MULTI_NONE) {
        *buf++ = dst->addr[1];  /* 目标网络号 */
        *buf++ = dst->addr[2];  /* 目标MAC地址 */
    }

    /* 添加源端口 */
    *buf++ = src->port & 0xff;  /* 端口低字节 */
    if (src->port & 0xff00) {   /* 端口高字节存在 */
        *buf++ = src->port >> 8;
        hdr[0] |= 2;            /* 设置端口高字节标志 */
    }

    /* 添加目标端口 */
    *buf++ = dst->port & 0xff;  /* 端口低字节 */
    if (dst->port & 0xff00) {   /* 端口高字节存在 */
        *buf++ = dst->port >> 8;
        hdr[0] |= 1;            /* 设置端口高字节标志 */
    }

    return buf - hdr;  /* 返回头部总大小 */
}

int cdn1_frame_w(cdn_pkt_t *pkt)
{
    uint8_t *frame = pkt->frm->dat;
    frame[0] = pkt->src.addr[2];
    frame[1] = pkt->dst.addr[2];
    int len = cdn1_hdr_w(pkt, frame + 3);
    if (len < 0)
        return len;
    frame[2] = pkt->len + len;
    return 0;
}

int cdn1_hdr_r(cdn_pkt_t *pkt, const uint8_t *hdr)
{
    cdn_sockaddr_t *src = &pkt->src;
    cdn_sockaddr_t *dst = &pkt->dst;

    const uint8_t *buf = hdr + 1;
    
    if ((hdr[0] & 0xc8) != 0x80)
        return CDN_ERR_ASSERT;
    
    cdn_multi_t multi = (cdn_multi_t)((hdr[0] >> 4) & 3);

    if (multi & CDN_MULTI_NET) {
        src->addr[0] = 0xa0;
        src->addr[1] = *buf++;
        src->addr[2] = *buf++;
    } else {
        src->addr[0] = 0x80;
        src->addr[1] = pkt->_l_net;
        src->addr[2] = pkt->_s_mac;
    }
    
    if (multi != CDN_MULTI_NONE) {
        dst->addr[0] = (multi & CDN_MULTI_CAST) ? 0xf0 : 0xa0;
        dst->addr[1] = *buf++;
        dst->addr[2] = *buf++;
    } else {
        dst->addr[0] = 0x80;
        dst->addr[1] = pkt->_l_net;
        dst->addr[2] = pkt->_d_mac;
    }

    src->port = *buf++;
    if (hdr[0] & 2)
        src->port |= *buf++ << 8;

    dst->port = *buf++;
    if (hdr[0] & 1)
        dst->port |= *buf++ << 8;

    return buf - hdr;
}

int cdn1_frame_r(cdn_pkt_t *pkt)
{
    uint8_t *frame = pkt->frm->dat;
    pkt->_s_mac = frame[0];
    pkt->_d_mac = frame[1];
    int len = cdn1_hdr_r(pkt, frame + 3);
    if (len < 0)
        return len;
    pkt->dat = frame + 3 + len;
    pkt->len = frame[2] - len;
    return 0;
}